#!/usr/local/bin/python3

#import sys
import SocketServer
#import socket
#import string
#import Queue
import threading
#import thread
#import time
#sys.path.insert(0, '/usr/share/pyshared/daemon')
#import daemon
import logger
import dlmhelpers

class MemberContext():
    def __init__(self, name, address, port, tx_queue, rply_queue):
        self.name = name
        self.alias = None
        self.address = address
        self.port = port
        self.tx_queue = tx_queue
        self.alive = True
        #NOTE: Change to reply?
        self.reply = rply_queue
        
    def __str__(self):
        return self.name
        
    
class TcpDaemon(SocketServer.BaseRequestHandler):
    _members = dict()
    _alias_map = dict()
    _output_queue = None
    # TODO: Change loggers to False before release
    _logger = Log('TcpDaemon', True)
    
    def setup(self):    
        # *** Setup new client ***
        # Retrive client name
        name = "CLNT{0}".format(TcpDaemon._get_count())
        # Retrive client address and port
        (address, port) = self.client_address
        # Make a new message queue
        queue = Queue.Queue()         
        # Make new request queue
        r_queue = Queue.Queue()
        # Make new client context
        self.my_context = MemberContext(name, address, port, queue, r_queue)
        # Add client to members dictionary
        TcpDaemon._add_member(self.my_context):
        
        # Log activity
        TcpDaemon._logger.log(
            "Client {0} (aka: {1}) just connected!".format(
                address, 
                name
            )
        )        
        
        # Fire off sender thread
        threading.Thread(
            target=TcpDaemon.handle_send, 
            args=(self, None)
        ).start() # Start Tx thread.
        
        # Fire off greet timer on new thread
        threading.Timer(
            2.0, 
            TcpDaemon.greet, 
            [self]
        ).start() # Fire off greeting in 2 seconds.
   
    #TODO: A lot
    def handle(self):
        try:
            while self.my_context.alive :
                rx_message = self.request.recv(1024)
                message = Helpers.process(rx_message)
                
                if message is None:
                    continue
                                
                TcpDaemon._logger.log(
                    "TCP RX: Msg from " \
                    "{0} (aka {1}): {2}".format(
                        self.my_context.address, 
                        self.my_context.name, 
                        rx_message
                    )
                )
                
                #TODO: Implement new command verification scheme
                # if lookup(myMsg, CommandList):
                if message[0] == "BYE":
                    self.my_context.queue.put('<KILL>')
                    self.my_context.alive = False
                    continue
                elif message[0] == "HELLO":
                    if len(message) == 2:
                        alias = str(messasge[1])
                        self._register_alias(alias)
                else:
                    #TODO: change var to queue
                    if self.my_context.reply.empty():
                        #TODO: Implement a method to handle 
                        # non-soliceted responses, i.e. peer requests
                        pass
                    else:
                        reply = self.my_context.reply.get()
                        reply.Reply = "<{0}:{1}>".format(
                            message[0], 
                            self.my_context.alias
                        )
                        #TODO: Need output msg pool
                        inMsgPool.put(reply)

        except Exception as ex:
            if _debug_:
                print("TCP Read Exception", ex)
            self.killed = True
            
    # TODO: Nothing (i hope)
    def handle_send(self, args):
        try:
            # While im alive dispach outgoing messages
            while self.my_context.alive:
                # Block on queue read
                request = self.my_context.queue.get()
                # If, for some reason, the request is null, 
                # discard and go around
                if request is None:
                    continue
                
                if type(request) == str:
                    # If the request is a string, its
                    # probably bad news for the thread
                    TcpDaemon._logger.log(
                        "TCP TX: Msg for " \
                        "{0} (aka {1}) : {2}".format(
                            self.my_contex.address, 
                            self.my_context.name, 
                            request
                        )
                    )
                    # Get the request text
                    request = Helpers.process(request)
                    # Test for death
                    if request[0] == 'KILL':
                        TcpDaemon._logger.log(
                            "{0} just died.".format(
                                self.my_context.name
                            )
                        )
                        self.my_context.alive = False
                        self.request.shutdown(socket.SHUT_RD)
                        continue
                        
                if type(request) == Message:
                    if not request.Digested:
                        TcpDaemon._logger.log(
                            "TCP TX: Msg for " \
                            "{0} (aka {1} = {2} ): {3}".format(
                                self.my_context.address, 
                                self.my_context.alias, 
                                self.my_context.name, 
                                request.Message
                            )
                        )
                        tx_message = request.Message
                        self.request.send(
                            str.encode(
                                "<{0}>\r\n".format(tx_message)
                            )
                        )
                        self.my_context.reply.put(msg)

        except Exception as ex:
            TcpDaemon._logger.log("TCP Send Exception", ex)
            self.my_context.alive = False
            self.request.shutdown(socket.SHUT_RD)
    
    # TODO: Alias removal
    def finish(self):
        try:
            TcpDaemon._logger.log(
                "Client {0} just disconnected!".format(
                    self.my_context.address
                )
            )
            # Remove this context from the dictionary
            TcpDaemon._remove_member(self.my_context)
            # Remove my alias
            self._remove_alias()
            # Goodbye horses
            self.request.send(str.encode('<BYE>\r\n'))
            # Im crying over you
            self.request.shutdown(socket.SHUT_WR)
        except Exception as ex:
            TcpDaemon._logger.log(
                "Finnish Exception: " + 
                self..my_context.name + " " + 
                ex
            )
            else:
                pass

    def greet(self):
        try:
            self.request.send(str.encode('<HELLO>\r\n'))
        except:
            pass
        
# ********* Private Instance Methods *********
    def _register_alias(self, alias):
        TcpDaemon._alias_map[alias] = self.my_context.name
        self.my_context.alias = alias
    
    def _remove_alias(self):
        TcpDaemon._alias_map.pop(self.my_context.alias, None)
        self.my_context.alias = None
        
# ********** Private Static Methods ********** 

    # Method used to abstract the addition of tcp members
    @staticmethod
    def _add_member(member):
        if member is not None and type(member) == MemberContext:
            TcpDaemon._members[member.name] = member
    # Method used to abstract the removal of tcp members.
    @staticmethod
    def _remove_member(member):
        if member is not None and type(member) == MemberContext:
            TcpDaemon._members.pop(member, None)

    # Method used to test for membership
    @staticmethod
    def _member_exsists(member):
        if member is not None and type(member) == MemberContext:
            if member.name in TcpDaemon._members.keys():
                return True
            else:
                return False

    # Method used to test for zombie membership
    @staticmethod
    def _get_zombies():
        zombies = sorted(
            list(
                set(TcpDaemon._members.keys()) -
                set(TcpDaemon._alias_map.keys())
                )
        )
        return zombies
    
    # This method places outgoing messages into the
    # externally deinfed ouput message queue
    @staticmethod
    def _put_output_queue(arg):
        TcpDaemon._output_queue.put(arg)
        
# ********** Public Static Methods **********     

    @staticmethod
    def SendMsg(msg, member_name=None):
        # No member specified, safe to assume normal msg
        if message is None:
            if TcpDaemon._member_exsists(msg.Name):
               TcpDaemon._members[msg.Name].queue.put(msg)
        # Member given, assume special (text) message
        else:
            if TcpDaemon._member_exsists(member_name):
                TcpDaemon._members[member_name].queue.put(msg)
    
    @staticmethod
    def SetQueue(queue):
        TcpDaemon._output_queue = queue
        
    @staticmethod
    def Purge():
        zombies = TcpDaemon._get_zombies()
        TcpDaemon.Killer(zombies)
        
    @staticmethod
    def KillAll():
        for member_name, member_context in TcpDaemon._members:
          TcpDaemon.Kill(member_context)

    @staticmethod
    def Killer(kill_list):
        #killList = NameList - set(AliasList.viewvalues())
        if kill_list is not None:
            if _debug_:
                print("Kill list: {0}".format(kill_list))
            for doomed in kill_list:
                TcpDaemon.Kill(doomed)

    @staticmethod
    def Kill(member):
        TcpDaemon.SendMsg("<KILL>", member.name)
        if _debug_:
            print("Killed: {0}".format(member.name))

